//
// Created by tang on 22-9-12.
//

#include "vulkan_app_base.hpp"
#include "window.hpp"

#include <string>
#include <set>

#if NDEBUG
constexpr bool debugEnabled = false;
#else
constexpr bool debugEnabled = true;
#endif

constexpr u32 max_in_flight_image = 2;

namespace core::app {
    static VulkanApp *app_instance = nullptr;

    inline namespace details {
        using namespace vk;

        static PFN_vkCreateDebugUtilsMessengerEXT
            create_debug_utils_messenger_ext = nullptr;
        static PFN_vkDestroyDebugUtilsMessengerEXT
            destroy_debug_utils_messenger_ext = nullptr;

        static VKAPI_ATTR VkBool32 VKAPI_CALL
        debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
                      VkDebugUtilsMessageTypeFlagsEXT,
                      const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData,
                      void *)
        {
            LOG_W_IF(messageSeverity >=
                         VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT,
                     "{}", pCallbackData->pMessage);
            return VK_FALSE;
        }

        i32 ratePhysicalDevice(PhysicalDevice device)
        {
            [[maybe_unused]] auto const properties = device.getProperties();
            [[maybe_unused]] auto const features   = device.getFeatures();
            i32                         score      = 0;
            switch (properties.deviceType) {
            case PhysicalDeviceType::eOther:
                score += 1;
                break;
            case PhysicalDeviceType::eIntegratedGpu:
                score += 100;
                break;
            case PhysicalDeviceType::eDiscreteGpu:
                score += 500;
                break;
            case PhysicalDeviceType::eVirtualGpu:
                score += 200;
                break;
            case PhysicalDeviceType::eCpu:
                score += 10;
                break;
            default:
                break;
            }
            return score;
        }

        struct QueueFamilyIndices {
            i32 graphics_idx {-1};
            i32 present_idx {-1};

            [[nodiscard]] b8 isCompleted() const
            {
                return graphics_idx >= 0 && present_idx >= 0;
            }
        };

        struct SwapChainSupportDetails {
            SwapChainSupportDetails(PhysicalDevice device, SurfaceKHR surface)
            {
                capabilities = device.getSurfaceCapabilitiesKHR(surface);
                formats      = device.getSurfaceFormatsKHR(surface);
                presentModes = device.getSurfacePresentModesKHR(surface);
            }

            SurfaceCapabilitiesKHR        capabilities;
            std::vector<SurfaceFormatKHR> formats;
            std::vector<PresentModeKHR>   presentModes;
        };

        QueueFamilyIndices findQueueFamilies(PhysicalDevice device,
                                             SurfaceKHR     surface)
        {
            QueueFamilyIndices                 indices {};
            std::vector<QueueFamilyProperties> families =
                device.getQueueFamilyProperties();

            int *queues[] {&indices.graphics_idx, &indices.present_idx};

            for (i8 i = 0; i < 2; ++i) {
                int index = 0;
                for (auto const &prop : families) {
                    if (prop.queueCount > 0 &&
                        prop.queueFlags &
                            ((i == 0) ? QueueFlagBits::eGraphics
                                      : QueueFlagBits::eTransfer)) {
                        if (i == 1) {
                            VkBool32 supportPresent =
                                device.getSurfaceSupportKHR(index, surface);
                            if (!supportPresent) {
                                *queues[i] = -1;
                            } else {
                                // 希望计算管线不和渲染管线重复
                                *queues[i] = index;
                                if (index != *queues[i - 1])
                                    break;
                            }

                        } else {
                            *queues[i] = index;
                            break;
                        }
                    }
                    index++;
                }
            }
            return indices;
        }

        b8 checkPhysicalDeviceExtensionsSupport(
            PhysicalDevice device, const std::vector<const char *> &extensions)
        {
            auto extProps = device.enumerateDeviceExtensionProperties();

            std::set<std::string> requiredExtensions {begin(extensions),
                                                      end(extensions)};
            for (auto const &ext : extProps) {
                requiredExtensions.erase(ext.extensionName);
            }

            return requiredExtensions
                .empty();  // all needed extension was found
        }

        b8 isDeviceSuitable(PhysicalDevice dev, SurfaceKHR surfaceKhr)
        {
            const std::vector<const char *> deviceExtensions {
                VK_KHR_SWAPCHAIN_EXTENSION_NAME};
            if (checkPhysicalDeviceExtensionsSupport(dev, deviceExtensions)) {
                const SwapChainSupportDetails swapChainSupportDetails {
                    dev, surfaceKhr};
                if (!swapChainSupportDetails.formats.empty() &&
                    !swapChainSupportDetails.presentModes.empty()) {
                    auto indices = findQueueFamilies(dev, surfaceKhr);
                    if (indices.isCompleted())
                        return true;
                }
            }
            return false;
        }

        PhysicalDevice pickPhysicalDevice(Instance   instance,
                                          SurfaceKHR surface,
                                          i32       &graphic_family,
                                          i32       &present_family)
        {
            PhysicalDevice result;

            auto devices = instance.enumeratePhysicalDevices();
            std::sort(begin(devices), end(devices),
                      [](PhysicalDevice a, PhysicalDevice b) {
                          return ratePhysicalDevice(a) >= ratePhysicalDevice(b);
                      });

            for (auto const &device : devices) {
                if (isDeviceSuitable(device, surface)) {
                    result             = device;
                    const auto indices = findQueueFamilies(device, surface);
                    graphic_family     = indices.graphics_idx;
                    present_family     = indices.present_idx;
                    LOG_I("Device {}, Graphics Queue #{}, Present Queue #{}",
                          (void *)device.operator VkPhysicalDevice(),
                          indices.graphics_idx, indices.present_idx);
                    break;
                }
            }
            CORE_ASSERT(result);
            return result;
        }

        SurfaceFormatKHR chooseSwapSurfaceFormat(
            const std::vector<SurfaceFormatKHR> &supportFormats)
        {
            CORE_ASSERT(!supportFormats.empty());
            if (supportFormats.size() == 1 &&
                supportFormats[0].format == Format::eUndefined) {
                return {Format::eB8G8R8A8Snorm, ColorSpaceKHR::eSrgbNonlinear};
            }
            for (auto const &f : supportFormats) {
                if (f.format == Format::eB8G8R8A8Unorm &&
                    f.colorSpace == ColorSpaceKHR::eSrgbNonlinear)
                    return f;
            }
            return supportFormats.front();
        }

        PresentModeKHR chooseSwapSurfacePresentMode(
            const std::vector<PresentModeKHR> &supportedModes)
        {
            PresentModeKHR bestMode = PresentModeKHR::eFifo;

            for (auto const &mode : supportedModes) {
                if (mode == PresentModeKHR::eMailbox) {
                    bestMode = mode;  // Preferred Mode
                    break;
                } else if (mode == PresentModeKHR::eImmediate) {
                    bestMode = mode;
                }
            }
            return bestMode;
        }

        Extent2D
        chooseSwapSurfaceExtend2D(const SurfaceCapabilitiesKHR &capabilities,
                                  Extent2D                      screenSize)
        {
            Extent2D result {0, 0};

            // we shout match screenSize if available
            if (capabilities.currentExtent.width !=
                std::numeric_limits<u32>::max()) {
                result = capabilities.currentExtent;
            } else {
                result = screenSize;
                // clamp to max / min
                result.width =
                    std::clamp(result.width, capabilities.minImageExtent.width,
                               capabilities.maxImageExtent.width);
                result.height = std::clamp(result.height,
                                           capabilities.minImageExtent.height,
                                           capabilities.maxImageExtent.height);
            }

            CORE_ASSERT(result.width > 0 && result.height > 0);
            return result;
        }

    }  // namespace details

    void VulkanApp::AttachWindow(Window *window)
    {
        m_window = window->WindowHandle();
        m_input  = window->GetInput();

        // create instance, select physical device, create logic device
        VulkanInit();
        OnInitialize();
        OnWindowResized();
    }

    VulkanApp::VulkanApp() :
        m_window {},
        m_graphic_family {-1},
        m_present_family {-1},
        m_current_frame {0}
    {
        CORE_ASSERT(app_instance == nullptr);
        app_instance = this;
    }

    void VulkanApp::DetachWindow()
    {
        m_device->waitIdle();
        OnCleanup();
    }

    VulkanApp::~VulkanApp() = default;

    void VulkanApp::VulkanInit()
    {
        using namespace vk;

        {
            // instance
            ApplicationInfo applicationInfo {
                .pApplicationName   = "ExampleApp",
                .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
                .pEngineName        = "No Engine",
                .engineVersion      = VK_MAKE_VERSION(0, 0, 0),
                .apiVersion         = VK_API_VERSION_1_1};
            InstanceCreateInfo        createInfo {.pApplicationInfo =
                                               &applicationInfo};
            std::vector<const char *> required_ext {};
            u32                       glfw_ext_cnt = 0;
            const char              **glfw_ext =
                glfwGetRequiredInstanceExtensions(&glfw_ext_cnt);
            std::copy(glfw_ext, glfw_ext + glfw_ext_cnt,
                      std::back_inserter(required_ext));

            if constexpr (debugEnabled) {
                static const char *const debug_layer_name =
                    "VK_LAYER_KHRONOS_validation";
                // check if layer is support
                bool support = false;
                for (auto const &prop : enumerateInstanceLayerProperties()) {
                    if (strcmp(prop.layerName, debug_layer_name) == 0) {
                        support = true;
                        break;
                    }
                }

                if (support) {
                    LOG_D("Vulkan: Debug Validation Layer Enabled!");
                    required_ext.emplace_back(
                        VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
                    createInfo.enabledLayerCount   = 1;
                    createInfo.ppEnabledLayerNames = &debug_layer_name;
                }
            }

            createInfo.enabledExtensionCount   = (u32)required_ext.size();
            createInfo.ppEnabledExtensionNames = required_ext.data();

            m_instance = createInstanceUnique(createInfo);
            CORE_ASSERT(m_instance);

            create_debug_utils_messenger_ext =
                (PFN_vkCreateDebugUtilsMessengerEXT)m_instance->getProcAddr(
                    "vkCreateDebugUtilsMessengerEXT");
            destroy_debug_utils_messenger_ext =
                (PFN_vkDestroyDebugUtilsMessengerEXT)m_instance->getProcAddr(
                    "vkDestroyDebugUtilsMessengerEXT");
        }

        if constexpr (debugEnabled) {
            // setup debug callback
            DebugUtilsMessengerCreateInfoEXT createInfoExt {
                .messageSeverity =
                    DebugUtilsMessageSeverityFlagBitsEXT::eVerbose |
                    DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
                    DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
                    DebugUtilsMessageSeverityFlagBitsEXT::eError,
                .messageType = DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
                               DebugUtilsMessageTypeFlagBitsEXT::ePerformance |
                               DebugUtilsMessageTypeFlagBitsEXT::eValidation,
                .pfnUserCallback = &debugCallback,
                .pUserData       = nullptr};
            m_debug_utils =
                m_instance->createDebugUtilsMessengerEXTUnique(createInfoExt);
        }

        {
            // create surface
            VkSurfaceKHR surface {VK_NULL_HANDLE};
            CORE_ASSERT(glfwCreateWindowSurface(*m_instance, m_window, nullptr,
                                                &surface) == VK_SUCCESS);
            m_surface = UniqueSurfaceKHR(surface, m_instance.get());
        }

        {
            // select device
            m_physical_device = pickPhysicalDevice(
                *m_instance, *m_surface, m_graphic_family, m_present_family);
        }

        {
            // logic device
            std::vector<const char *> deviceExtensions {
                VK_KHR_SWAPCHAIN_EXTENSION_NAME};

            DeviceCreateInfo createInfo {
                .enabledExtensionCount   = (u32)deviceExtensions.size(),
                .ppEnabledExtensionNames = deviceExtensions.data()};

            const float           queue_priorities[] {1.f};
            DeviceQueueCreateInfo queues[2] {
                {.queueFamilyIndex = (u32)m_graphic_family,
                 .queueCount       = 1,
                 .pQueuePriorities = queue_priorities},
                {.queueFamilyIndex = (u32)m_present_family,
                 .queueCount       = 1,
                 .pQueuePriorities = queue_priorities}};
            createInfo.queueCreateInfoCount =
                m_graphic_family == m_present_family ? 1 : 2;
            createInfo.pQueueCreateInfos = queues;

            PhysicalDeviceFeatures features;
            createInfo.pEnabledFeatures = &features;

            if constexpr (debugEnabled) {
                static const char *const debugLayerName =
                    VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
                createInfo.enabledLayerCount   = 1;
                createInfo.ppEnabledLayerNames = &debugLayerName;
            } else {
                createInfo.enabledLayerCount = 0;
            }

            m_device         = m_physical_device.createDeviceUnique(createInfo);
            m_graphics_queue = m_device->getQueue(m_graphic_family, 0);
            m_present_queue  = m_device->getQueue(m_present_family, 0);
        }

        {
            // DescriptorSetLayout
            auto dsl = OnCreateDescriptorSetLayout();
            if (dsl)
                m_descriptor_set_layout = UniqueDescriptorSetLayout(dsl, m_device.get());
        }

        {
            // pipeline layout
            PipelineLayoutCreateInfo plci {.setLayoutCount         = 0,
                                           .pushConstantRangeCount = 0};
            auto dsl = m_descriptor_set_layout.get();
            if (dsl) {
                plci.setLayoutCount = 1;
                plci.pSetLayouts = &dsl;
            }
            m_pipeline_layout = m_device->createPipelineLayoutUnique(plci);
        }

        {
            // command pool
            CommandPoolCreateInfo cpci {
                .flags = CommandPoolCreateFlagBits::eResetCommandBuffer,
                .queueFamilyIndex = (u32)m_graphic_family};
            m_command_pool = m_device->createCommandPoolUnique(cpci);
        }

        {
            // descriptor pool
            DescriptorPoolSize poolSize{
                .type = DescriptorType::eUniformBuffer,
                .descriptorCount = MaxInFlightImageCount()
            };

            DescriptorPoolCreateInfo dpci{
                .flags = DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
                .maxSets = MaxInFlightImageCount(),
                .poolSizeCount = 1,
                .pPoolSizes = &poolSize,
            };

            m_descriptor_pool = m_device->createDescriptorPoolUnique(dpci);
        }

        {
            // common arguments
            SwapChainSupportDetails supportDetails {m_physical_device,
                                                    *m_surface};
            m_image_count = supportDetails.capabilities.minImageCount + 1;
            if (supportDetails.capabilities.maxImageCount > 0 &&
                m_image_count > supportDetails.capabilities.maxImageCount)
                m_image_count = supportDetails.capabilities.maxImageCount;

            CORE_ASSERT_MSG(
                m_image_count >= max_in_flight_image,
                "m_image_count(%u) is less than max_in_flight_image(%u)",
                m_image_count, max_in_flight_image);

            m_surface_format = chooseSwapSurfaceFormat(supportDetails.formats);
            m_present_mode =
                chooseSwapSurfacePresentMode(supportDetails.presentModes);
            m_transform = supportDetails.capabilities.currentTransform;
        }
    }

    void VulkanApp::ReCreateSwapchianAndStuff()
    {
        using namespace vk;

        // get window size
        i32 width, height;
        glfwGetWindowSize(m_window, &width, &height);
        m_swapchian_size = Extent2D{(u32)width, (u32)height};

        m_device->waitIdle();

        {
            // create swap chain
            SwapchainCreateInfoKHR sci {
                .surface = *m_surface,
                .clipped = true,
            };

            sci.minImageCount   = m_image_count;
            sci.imageFormat     = m_surface_format.format;
            sci.imageColorSpace = m_surface_format.colorSpace;
            sci.presentMode     = m_present_mode;
            sci.imageExtent     = chooseSwapSurfaceExtend2D(
                m_physical_device.getSurfaceCapabilitiesKHR(*m_surface),
                m_swapchian_size);
            sci.imageArrayLayers = 1;
            sci.imageUsage       = ImageUsageFlagBits::eColorAttachment;

            u32 indices[2] {(u32)m_graphic_family, (u32)m_present_family};
            if (m_graphic_family != m_present_family) {
                sci.imageSharingMode      = SharingMode::eConcurrent;
                sci.queueFamilyIndexCount = 2;
                sci.pQueueFamilyIndices   = indices;
            } else {
                sci.imageSharingMode      = SharingMode::eExclusive;
                sci.queueFamilyIndexCount = 0;
            }

            sci.preTransform   = m_transform;
            sci.compositeAlpha = CompositeAlphaFlagBitsKHR::eOpaque;
            sci.oldSwapchain   = m_swapchain.get();

            m_swapchain = m_device->createSwapchainKHRUnique(sci);
            m_images    = m_device->getSwapchainImagesKHR(*m_swapchain);
        }

        {
            // create image views
            m_views.clear();
            m_views.reserve(m_images.size());
            for (auto &m_image : m_images) {
                ImageViewCreateInfo createInfo {
                    .image            = m_image,
                    .viewType         = ImageViewType::e2D,
                    .format           = m_surface_format.format,
                    .components       = {ComponentSwizzle::eIdentity,
                                         ComponentSwizzle::eIdentity,
                                         ComponentSwizzle::eIdentity,
                                         ComponentSwizzle::eIdentity},
                    .subresourceRange = {.aspectMask =
                                             ImageAspectFlagBits::eColor,
                                         .baseMipLevel   = 0,
                                         .levelCount     = 1,
                                         .baseArrayLayer = 0,
                                         .layerCount     = 1}};

                m_views.emplace_back(
                    m_device->createImageViewUnique(createInfo));
            }
        }

        {
            // sync objects
            m_image_available_semaphores.clear();
            m_render_finished_semaphores.clear();
            m_in_flight_fences.clear();

            for (u32 i = 0; i < max_in_flight_image; ++i) {
                SemaphoreCreateInfo sci {};
                m_image_available_semaphores.emplace_back(
                    m_device->createSemaphoreUnique(sci));
                m_render_finished_semaphores.emplace_back(
                    m_device->createSemaphoreUnique(sci));

                FenceCreateInfo fci {.flags = FenceCreateFlagBits::eSignaled};
                m_in_flight_fences.emplace_back(
                    m_device->createFenceUnique(fci));
            }
        }

        {
            // render_pass
            AttachmentDescription colorAttachment {
                .format         = m_surface_format.format,
                .samples        = SampleCountFlagBits::e1,
                .loadOp         = AttachmentLoadOp::eClear,
                .storeOp        = AttachmentStoreOp::eStore,
                .stencilLoadOp  = AttachmentLoadOp::eDontCare,
                .stencilStoreOp = AttachmentStoreOp::eDontCare,
                .initialLayout  = ImageLayout::eUndefined,
                .finalLayout    = ImageLayout::ePresentSrcKHR};

            AttachmentReference colorAttachmentRef {
                .attachment = 0,
                .layout     = ImageLayout::eColorAttachmentOptimal};

            SubpassDescription subpass {
                .pipelineBindPoint    = PipelineBindPoint::eGraphics,
                .colorAttachmentCount = 1,
                .pColorAttachments    = &colorAttachmentRef,
            };

            SubpassDependency dependency {
                .srcSubpass    = VK_SUBPASS_EXTERNAL,
                .dstSubpass    = 0,
                .srcStageMask  = PipelineStageFlagBits::eColorAttachmentOutput,
                .dstStageMask  = PipelineStageFlagBits::eColorAttachmentOutput,
                .srcAccessMask = {},
                .dstAccessMask = AccessFlagBits::eColorAttachmentRead |
                                 AccessFlagBits::eColorAttachmentWrite,
            };

            RenderPassCreateInfo rpci {.attachmentCount = 1,
                                       .pAttachments    = &colorAttachment,
                                       .subpassCount    = 1,
                                       .pSubpasses      = &subpass,
                                       .dependencyCount = 1,
                                       .pDependencies   = &dependency};

            m_render_pass = m_device->createRenderPassUnique(rpci);
        }

        // pipeline need subclass to config
        m_render_pipeline = UniquePipeline {
            OnCreateGraphicsPipeline(m_swapchian_size),
            m_device.get()};

        {
            // frame buffer
            m_frame_buffers.clear();
            m_frame_buffers.reserve(m_views.size());
            for (auto &view : m_views) {
                ImageView views[] {*view};

                FramebufferCreateInfo fci {
                    .renderPass      = m_render_pass.get(),
                    .attachmentCount = 1,
                    .pAttachments    = views,
                    .width           = m_swapchian_size.width,
                    .height          = m_swapchian_size.height,
                    .layers          = 1,
                };

                m_frame_buffers.emplace_back(
                    m_device->createFramebufferUnique(fci));
            }
        }

        {
            // command buffers
            CommandBufferAllocateInfo cbai {
                .commandPool        = *m_command_pool,
                .level              = CommandBufferLevel::ePrimary,
                .commandBufferCount = max_in_flight_image};
            m_command_buffers = m_device->allocateCommandBuffersUnique(cbai);
        }
    }

    std::tuple<vk::UniqueBuffer, vk::UniqueDeviceMemory>
    VulkanApp::CreateBuffer(vk::DeviceSize          bufferSize,
                            vk::BufferUsageFlags    usage,
                            vk::MemoryPropertyFlags property)
    {
        using namespace vk;
        BufferCreateInfo bci {
            .size        = bufferSize,
            .usage       = usage,
            .sharingMode = SharingMode::eExclusive,
        };

        auto buffer       = m_device->createBufferUnique(bci);
        auto requirements = m_device->getBufferMemoryRequirements(buffer.get());
        auto all_prop     = m_physical_device.getMemoryProperties();

        u32 memId = 0;
        for (; memId < all_prop.memoryTypeCount; ++memId) {
            if ((requirements.memoryTypeBits & (1 << memId)) &&
                (all_prop.memoryTypes[memId].propertyFlags & property) ==
                    property)
                break;
        }

        CORE_ASSERT_MSG(memId < all_prop.memoryTypeCount,
                        "Could not found suitable memory!!");

        MemoryAllocateInfo mai {
            .allocationSize  = bufferSize,
            .memoryTypeIndex = memId,
        };
        auto memory = m_device->allocateMemoryUnique(mai);
        m_device->bindBufferMemory(buffer.get(), memory.get(), 0);
        return std::make_tuple(std::move(buffer), std::move(memory));
    }

    void VulkanApp::UploadBufferData(vk::DeviceMemory memory, const void *data,
                                     vk::DeviceSize length,
                                     vk::DeviceSize offset)
    {
        using namespace vk;
        void *device_mem = m_device->mapMemory(memory, offset, length, {});
        CORE_ASSERT(data != nullptr);
        memcpy(device_mem, data, length);
        MappedMemoryRange range {
            .memory = memory,
            .offset = offset,
            .size   = length,
        };
        m_device->flushMappedMemoryRanges(range);
        m_device->unmapMemory(memory);
    }

    void VulkanApp::Update()
    {
        auto constexpr timeout = std::numeric_limits<u64>::max();
        Result r {Result::eSuccess};
        r = m_device->waitForFences(*m_in_flight_fences[m_current_frame], true,
                                    timeout);
        CORE_ASSERT(r == Result::eSuccess);
        m_device->resetFences(*m_in_flight_fences[m_current_frame]);

        u32 idx;
        std::tie(r, idx) = m_device->acquireNextImageKHR(
            *m_swapchain, timeout,
            *m_image_available_semaphores[m_current_frame], nullptr);
        if (r != Result::eSuccess) {
            LOG_I("Need Rebuild Swap Chain!!");
            OnWindowResized();
            m_current_frame = 0;
            return;
        }

        Semaphore     wait[] {*m_image_available_semaphores[m_current_frame]};
        Semaphore     signal[] {*m_render_finished_semaphores[m_current_frame]};
        CommandBuffer commandBuffer = *m_command_buffers[m_current_frame];
        OnRecordRenderCommand(commandBuffer, m_current_frame, idx,
                              m_swapchian_size);
        OnUpdateBuffers(idx, m_current_frame);
        PipelineStageFlags waitStages[] {
            PipelineStageFlagBits::eColorAttachmentOutput};

        SubmitInfo submitInfo {
            .waitSemaphoreCount   = 1,
            .pWaitSemaphores      = wait,
            .pWaitDstStageMask    = waitStages,
            .commandBufferCount   = 1,
            .pCommandBuffers      = &commandBuffer,
            .signalSemaphoreCount = 1,
            .pSignalSemaphores    = signal,
        };

        m_graphics_queue.submit(submitInfo,
                                *m_in_flight_fences[m_current_frame]);

        SwapchainKHR   swapchains[] {*m_swapchain};
        Result         presentResult {Result::eSuccess};
        PresentInfoKHR presentInfoKhr {
            .waitSemaphoreCount = 1,
            .pWaitSemaphores    = signal,
            .swapchainCount     = 1,
            .pSwapchains        = swapchains,
            .pImageIndices      = &idx,
            .pResults           = &presentResult,
        };

        r = m_present_queue.presentKHR(presentInfoKhr);
        CORE_ASSERT(r == Result::eSuccess || r == Result::eSuboptimalKHR);
        CORE_ASSERT_MSG(presentResult == Result::eSuccess ||
                            r == Result::eSuboptimalKHR,
                        "Result is {}", (u32)presentResult);

        m_present_queue.waitIdle();
        m_current_frame = (m_current_frame + 1) % max_in_flight_image;

        // _sleep(500);
    }

    void VulkanApp::CopyBuffer(vk::Buffer src, vk::Buffer dst,
                               vk::DeviceSize size)
    {
        CORE_ASSERT(src && dst && size);
        CommandBufferAllocateInfo cbai {
            .commandPool        = m_command_pool.get(),
            .level              = CommandBufferLevel::ePrimary,
            .commandBufferCount = 1,
        };
        auto  buffers = m_device->allocateCommandBuffersUnique(cbai);
        auto &buffer  = buffers.front();
        CommandBufferBeginInfo cbbi {
            .flags            = CommandBufferUsageFlagBits::eOneTimeSubmit,
            .pInheritanceInfo = nullptr,
        };

        BufferCopy cb {
            .srcOffset = 0,
            .dstOffset = 0,
            .size      = size,
        };

        buffer->begin(cbbi);
        buffer->copyBuffer(src, dst, cb);
        buffer->end();

        CommandBuffer submit_buffers[] {buffer.get()};

        SubmitInfo si {
            .commandBufferCount = 1,
            .pCommandBuffers    = submit_buffers,
        };

        m_graphics_queue.submit(si);
        m_graphics_queue.waitIdle();
    }

    u32 VulkanApp::MaxInFlightImageCount() { return max_in_flight_image; }

}  // namespace core::app

// implement for vkCreateDebugUtilsMessengerEXT and
// vkDestroyDebugUtilsMessengerEXT
VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(
    VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo,
    const VkAllocationCallbacks *pAllocate,
    VkDebugUtilsMessengerEXT    *pMessenger)
{
    if (core::app::create_debug_utils_messenger_ext) {
        return core::app::create_debug_utils_messenger_ext(
            instance, pCreateInfo, pAllocate, pMessenger);
    }
    return VK_ERROR_EXTENSION_NOT_PRESENT;
}

VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT(
    VkInstance instance, VkDebugUtilsMessengerEXT messenger,
    const VkAllocationCallbacks *pCallback)
{
    if (core::app::destroy_debug_utils_messenger_ext)
        core::app::destroy_debug_utils_messenger_ext(instance, messenger,
                                                     pCallback);
}
